﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using XcoAppSpaces.Core;
using XcoAppSpaces.Contracts.Exceptions;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Core.PortWiring;
using XcoAppSpaces.Core.Ports;

namespace test.XcoAppSpaces.Core.PortWiring
{
	#region Test Workers with Concurrent/Exclusive/Teardown
    class SinglePortWorker : Port<string>
    {
        [XcoConcurrent]
        public void ProcessString(string msg){}
    }

	class SinglePortWorkerInvalidProcessor : Port<string>
	{
		[XcoConcurrent]
		public void ProcessString(string msg, int invalidParam) { }
	}


	class WorkerContract : PortSet<int, string, bool>
    {
        public AutoResetEvent are = new AutoResetEvent(false);
    }

    class MyWorker : WorkerContract
    {
        public int intMsg;
        public string strMsg;

        [XcoExclusive]
        public void ProcessInt(int msg)
        {
            this.intMsg = msg;
        }

        [XcoConcurrent]
        protected void ProcessString(string msg)
        {
            this.strMsg = msg;
        }

		[XcoConcurrent]
        public void ProcessBool(bool msg)
        {
            base.are.Set();
        }
	}

	class MyDerivedWorker : MyWorker {}

	class MyWorkerInvalid : WorkerContract
	{
		[XcoExclusive]
		public void ProcessInt(int msg)
		{
		}

		[XcoConcurrent]
		public string ProcessString(string msg) //invalid return type
		{
			return msg;
		}

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class MyWorkerInvalid2 : WorkerContract //missing message processor for type string
	{
		[XcoExclusive]
		public void ProcessInt(int msg)
		{
		}

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class MyWorkerInvalid3MultiProcessorsOfSameType : WorkerContract //multiple processors of message type int
	{
		[XcoExclusive]
		public void ProcessInt(int msg)
		{
		}

		[XcoExclusive]
		public void ProcessInt2(int msg)
		{
		}
	}
	#endregion

	#region Test Iterative Worker with Extension
	class MyIterativeWorker : WorkerContract
	{
		public WorkerExt ext = new WorkerExt();
		public int intMsg;
		public Port<int> portForIterator = new Port<int>();

		[XcoConcurrent]
		public async Task ProcessInt(int msg)
		{
			for (int i = 0; i < msg; i++)
			{
				Console.WriteLine("Receiving from int port...");
				this.intMsg = await portForIterator.Receive();
			}
		}

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
			Console.WriteLine("Bool received: " + msg);
			base.are.Set();
		}
	}
	#endregion

	#region Workers for Extension Testing
	class ExtensionWorkerInvalidContract : PortSet<int, bool>
	{
	}
	class ExtensionWorkerInvalid : ExtensionWorkerInvalidContract
	{
		public WorkerExt2 ext = new WorkerExt2();
		
		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerInvalid2 : ExtensionWorkerInvalidContract
	{
		[XcoWorkerExtension]
		public string invalidExtension = "test";

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerValidContract : PortSet<string, bool>
	{
	}
	class ExtensionWorkerValid : ExtensionWorkerValidContract
	{
		public WorkerExt2 ext = new WorkerExt2();
		
		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerAttrOverwriteContract : PortSet<int, bool>
	{
	}
	class ExtensionWorkerAttrOverwrite : ExtensionWorkerAttrOverwriteContract
	{
		[XcoWorkerExtension(typeof(int))]
		public WorkerExt2 ext = new WorkerExt2();

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerAttrOverwrite2 : ExtensionWorkerAttrOverwriteContract
	{
		[XcoWorkerExtension(Optional = new Type[]{typeof(string)})]
		public WorkerExt2 ext = new WorkerExt2();

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerAttrOverwrite3 : ExtensionWorkerAttrOverwriteContract
	{
		[XcoWorkerExtension(Required = new Type[] { typeof(int) })]
		public WorkerExt2 ext = new WorkerExt2();

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}

	class ExtensionWorkerAttrOverwrite4 : ExtensionWorkerAttrOverwriteContract
	{
		[XcoWorkerExtension(Optional = new Type[] { typeof(int), typeof(string) })]
		public WorkerExt2 ext = new WorkerExt2();

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}

		[XcoConcurrent]
		public void ProcessInt(int msg)
		{
		}
	}

	class ExtensionWorkerAttrOverwrite5 : ExtensionWorkerAttrOverwriteContract
	{
		[XcoWorkerExtension(Optional = new Type[] { typeof(string) })]
		public WorkerExt2 ext = new WorkerExt2();

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}

		[XcoConcurrent]
		public void ProcessInt(int msg)
		{
		}
	}

	class ExtensionWorkerDeepHierarchy : PortSet<string, int, bool>
	{
		public WorkerExtStep1 Step1 = new WorkerExtStep1();
	}

	class ExtensionWorkerDeepHierarchyOverwrite : PortSet<string, int, bool>
	{
		public int intMsg = 0;

		[XcoWorkerExtension(typeof(string))]
		public WorkerExtStep1 Step1 = new WorkerExtStep1();

		[XcoConcurrent]
		void ProcessInt(int msg)
		{
			intMsg = msg;
		}
	}
	#endregion

	#region Worker Extensions
	[XcoWorkerExtension]
	class WorkerExt
	{
		public string strMsg;

		[XcoConcurrent]
		public void ProcessString(string msg)
		{
			Console.WriteLine("WorkerExtension: String received: " + msg);
			strMsg = msg;
		}
	}

	[XcoWorkerExtension(typeof(string))]
	class WorkerExt2
	{
		public string strMsg;
		public int intMsg;

		[XcoConcurrent]
		public void ProcessString(string msg)
		{
			Console.WriteLine("WorkerExtension: String received: " + msg);
			strMsg = msg;
		}

		[XcoConcurrent]
		public void ProcessInt(int msg)
		{
			Console.WriteLine("WorkerExtension: Int received: " + msg);
			intMsg = msg;
		}
	}

	[XcoWorkerExtension]
	class WorkerExtStep1
	{
		public string strMsg;
		public WorkerExtStep2 step2 = new WorkerExtStep2();
		
		[XcoConcurrent]
		public void ProcessString(string msg)
		{
			Console.WriteLine("WorkerExtension Step 1: String received: " + msg);
			strMsg = msg;
		}
	}

	[XcoWorkerExtension]
	class WorkerExtStep2
	{
		public int intMsg;
		public WorkerExtStep3 step3 = new WorkerExtStep3();

		[XcoConcurrent]
		public void ProcessInt(int msg)
		{
			Console.WriteLine("WorkerExtension Step 2: int received: " + msg);
			intMsg = msg;
		}
	}

	[XcoWorkerExtension]
	class WorkerExtStep3
	{
		public bool boolMsg;

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
			Console.WriteLine("WorkerExtension Step 3: bool received: " + msg);
			boolMsg = msg;
		}
	}
	#endregion

	#region Worker with XcoServiceAttribute
	class MyXcoService1Attribute : XcoServiceAttribute
	{}
	class MyXcoService2Attribute : XcoServiceAttribute
	{}
	class MyXcoService3Attribute : XcoServiceAttribute
	{}

	[MyXcoService1]
	class WorkerWithServiceAttributes : WorkerContract
	{
		[MyXcoService2]
		private WorkerExt ext = new WorkerExt();
		
		[MyXcoService1, MyXcoService3]
		[XcoConcurrent]
		public void ProcessInt(int msg)
		{
		}

		[XcoConcurrent]
		public void ProcessBool(bool msg)
		{
		}
	}
	#endregion


	[TestFixture]
    public class testWorkerFactory
    {
		//private Dispatcher dp;
		//private DispatcherQueue dpq;
        private WorkerFactory wf;

        [SetUp]
        public void Setup()
        {
			//this.dp = new Dispatcher();
			//this.dpq = new DispatcherQueue("dpq", dp);

            this.wf = new WorkerFactory(); //uc);
        }

		//[TearDown]
		//public void Teardown()
		//{
		//	//this.dp.Dispose();
		//}

		[Test]
		public void TestCreate()
		{
			var workerInfo = wf.Create<WorkerContract, MyWorker>(null);
			Assert.AreEqual(3, workerInfo.ProcessorAttributes.Count);
			Assert.AreEqual(MessageTransferMode.Sequential, workerInfo.ProcessorAttributes[typeof(int)].MessageTransferMode);
			Assert.AreEqual(MessageTransferMode.Parallel, workerInfo.ProcessorAttributes[typeof(string)].MessageTransferMode);
			Assert.AreEqual(MessageTransferMode.Parallel, workerInfo.ProcessorAttributes[typeof(bool)].MessageTransferMode);
		}

        [Test]
        public void TestCreateAndPost()
        {
			var workerInfo = wf.Create<WorkerContract, MyWorker>(null);
        	WorkerContract wc = workerInfo.Instance;
			wc.Post(99);
            wc.Post("hello");
            wc.Post(true);
            Assert.IsTrue(wc.are.WaitOne(500, false));

			MyWorker mw = (MyWorker)wc;
	        SpinWait.SpinUntil(() => mw.intMsg != 0 && mw.strMsg != null, 1000);
			Assert.AreEqual(99, mw.intMsg);
            Assert.AreEqual("hello", mw.strMsg);
        }

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWithInvalidMsgProcessor()
		{
			WorkerContract wc = wf.Create<WorkerContract, MyWorkerInvalid>(null).Instance;
		}

		[Test]
		//[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWithMissingMessageProcessor()
		{
			WorkerContract wc = wf.Create<WorkerContract, MyWorkerInvalid2>(null).Instance;
			//should be working now, because wiring check disabled!!
		}

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWithDoubleIntMessageProcessor()
		{
			WorkerContract wc = wf.Create<WorkerContract, MyWorkerInvalid3MultiProcessorsOfSameType>(null).Instance;
		}

		[Test]
		public void TestCreateIterativeWorker()
		{
			WorkerContract wc = wf.Create<WorkerContract, MyIterativeWorker>(null).Instance;
			wc.Post(2);
			wc.Post("hello");
			wc.Post(true);
			Assert.IsTrue(wc.are.WaitOne(500, false));
			Thread.Sleep(100);
			
			MyIterativeWorker mw = (MyIterativeWorker)wc;
			Assert.AreEqual("hello", mw.ext.strMsg);
			//test iterative task (processing 2 messages because 2 was posted to the worker)
			Thread.Sleep(500);
			Assert.AreEqual(0, mw.intMsg);
			mw.portForIterator.Post(5);
			Thread.Sleep(100);
			Assert.AreEqual(5, mw.intMsg);
			mw.portForIterator.Post(10);
			Thread.Sleep(100);
			Assert.AreEqual(10, mw.intMsg);
			mw.portForIterator.Post(15);
			Thread.Sleep(100);
			Assert.AreEqual(10, mw.intMsg); //iterator not running any more, therefore 15 will not be processed
		}


		[Test]
		public void TestGetMessageProcessorMethods()
		{
			WorkerContract wc = new MyWorker();

			var processorMethods = wf.GetMessageProcessorMethods(wc);

			Assert.AreEqual(3, processorMethods.Count);
			Assert.AreEqual(2, processorMethods.Count(m => m.ProcessorAttr is XcoConcurrentAttribute));
			Assert.AreEqual(1, processorMethods.Count(m => m.ProcessorAttr is XcoExclusiveAttribute));
		}

		[Test]
		public void TestGetMessageProcessorMethods_DerivedType()
		{
			WorkerContract wc = new MyDerivedWorker();

			var processorMethods = wf.GetMessageProcessorMethods(wc);

			Assert.AreEqual(3, processorMethods.Count);
			Assert.AreEqual(2, processorMethods.Count(m => m.ProcessorAttr is XcoConcurrentAttribute));
			Assert.AreEqual(1, processorMethods.Count(m => m.ProcessorAttr is XcoExclusiveAttribute));
		}


		//[Test]
		//public void TestCreateCcrReceiverToBindProcessorMethodToPort()
		//{
		//	WorkerContract wc = new MyWorker();
		//	Dictionary<Type, IPort> messageContractPorts = wf.GetMessageContractPorts(wc);

		//	var processorMethods = wf.GetMessageProcessorMethods(wc);
		//	var processorMethod = processorMethods[0];

		//	var wiredProcessors = new Dictionary<Type, WorkerFactory.MessageProcessorInfo>();
		//	ReceiverTask rt = wf.CreateCcrReceiverToBindProcessorMethodToPort(messageContractPorts, processorMethod, this.dpq, true, wiredProcessors);
		//	Assert.IsNotNull(rt);
		//}

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWorkerWithExtensionInvalid()
		{
			//worker with missing port for required processor in extension
			ExtensionWorkerInvalidContract wc = wf.Create<ExtensionWorkerInvalidContract, ExtensionWorkerInvalid>(null).Instance;
		}

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWorkerWithExtensionInvalid2()
		{
			//worker with invalid extension
			ExtensionWorkerInvalidContract wc = wf.Create<ExtensionWorkerInvalidContract, ExtensionWorkerInvalid2>(null).Instance;
		}

		[Test]
		public void TestCreateWorkerWithExtensionValid()
		{
			var wc = wf.Create<ExtensionWorkerValidContract, ExtensionWorkerValid>(null).Instance;
			string teststr = "test";
			wc.Post(teststr);

			var mw = (ExtensionWorkerValid)wc;
			Thread.Sleep(100);
			Assert.AreEqual(teststr, mw.ext.strMsg);
		}

		[Test]
		public void TestCreateWorkerWithExtensionAttrOverwrite1()
		{
			var wc = wf.Create<ExtensionWorkerAttrOverwriteContract, ExtensionWorkerAttrOverwrite>(null).Instance;
			int testint = 234;
			wc.Post(testint);

			var mw = (ExtensionWorkerAttrOverwrite)wc;
			Thread.Sleep(100);
			Assert.AreEqual(testint, mw.ext.intMsg);
		}

		[Test]
		public void TestCreateWorkerWithExtensionAttrOverwrite2()
		{
			var wc = wf.Create<ExtensionWorkerAttrOverwriteContract, ExtensionWorkerAttrOverwrite2>(null).Instance;
			int testint = 234;
			wc.Post(testint);

			var mw = (ExtensionWorkerAttrOverwrite2)wc;
			Thread.Sleep(100);
			Assert.AreEqual(testint, mw.ext.intMsg);
		}

		[Test]
		public void TestCreateWorkerWithExtensionAttrOverwrite3()
		{
			var wc = wf.Create<ExtensionWorkerAttrOverwriteContract, ExtensionWorkerAttrOverwrite3>(null).Instance;
			int testint = 234;
			wc.Post(testint);

			var mw = (ExtensionWorkerAttrOverwrite3)wc;
			Thread.Sleep(100);
			Assert.AreEqual(testint, mw.ext.intMsg);
		}

		[Test]
		public void TestCreateWorkerWithExtensionAttrOverwrite4()
		{
			var wc = wf.Create<ExtensionWorkerAttrOverwriteContract, ExtensionWorkerAttrOverwrite4>(null).Instance;
			int testint = 234;
			wc.Post(testint);

			var mw = (ExtensionWorkerAttrOverwrite4)wc;
			Thread.Sleep(100);
			Assert.AreEqual(0, mw.ext.intMsg);
		}

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void TestCreateWorkerWithExtensionAttrOverwrite5()
		{
			var wc = wf.Create<ExtensionWorkerAttrOverwriteContract, ExtensionWorkerAttrOverwrite5>(null).Instance;
			int testint = 234;
			wc.Post(testint);

			var mw = (ExtensionWorkerAttrOverwrite5)wc;
			Thread.Sleep(100);
			Assert.AreEqual(0, mw.ext.intMsg);
		}

		[Test]
		public void TestCreateWorkerWithExtensionDeepHierarchy()
		{
			var w = wf.Create<ExtensionWorkerDeepHierarchy, ExtensionWorkerDeepHierarchy>(null).Instance;
			string teststr = "test";
			int testint = 234;

			w.Post(teststr);
			w.Post(testint);
			w.Post(true);

			Thread.Sleep(100);
			Assert.AreEqual(teststr, w.Step1.strMsg);
			Assert.AreEqual(testint, w.Step1.step2.intMsg);
			Assert.AreEqual(true, w.Step1.step2.step3.boolMsg);
		}

		[Test]
		public void TestCreateWorkerWithServiceAttributes()
		{
			var w = wf.Create<WorkerContract, WorkerWithServiceAttributes>(null);

			Assert.AreEqual(4, w.ServiceAttributes.Count);
			Assert.IsTrue(w.ServiceAttributes.Any(s => s.Attr is MyXcoService1Attribute && s.WorkerMessageType == null));
			Assert.IsTrue(w.ServiceAttributes.Any(s => s.Attr is MyXcoService2Attribute && s.WorkerMessageType == typeof(string)));
			Assert.IsTrue(w.ServiceAttributes.Any(s => s.Attr is MyXcoService1Attribute && s.WorkerMessageType == typeof(int)));
			Assert.IsTrue(w.ServiceAttributes.Any(s => s.Attr is MyXcoService3Attribute && s.WorkerMessageType == typeof(int)));
		}

		[Test]
		public void TestCreateWorkerWithExtensionDeepHierarchyOverwrite()
		{
			var sw = new Stopwatch();
			sw.Start();
			var w = wf.Create<ExtensionWorkerDeepHierarchyOverwrite, ExtensionWorkerDeepHierarchyOverwrite>(null).Instance;
			sw.Stop();
			Console.WriteLine("TestCreateWorkerWithExtensionDeepHierarchyOverwrite duration: " + sw.ElapsedMilliseconds);

			string teststr = "test";
			int testint = 234;

			w.Post(teststr);
			w.Post(testint);
			w.Post(true);

			Thread.Sleep(100);
			Assert.AreEqual(teststr, w.Step1.strMsg);
			Assert.AreEqual(0, w.Step1.step2.intMsg);
			Assert.AreEqual(234, w.intMsg);
			Assert.AreEqual(true, w.Step1.step2.step3.boolMsg);
		}

		[Test]
		[Ignore]
		public void TestPerformance()
		{
			var sw = new Stopwatch();
			sw.Start();
			for (int i = 0; i < 1000; i++)
				wf.Create<ExtensionWorkerDeepHierarchyOverwrite, ExtensionWorkerDeepHierarchyOverwrite>(null);
				//wf.Create<MyWorker, MyWorker>(null);
			sw.Stop();
			Console.WriteLine("TestCreateWorkerWithExtensionDeepHierarchyOverwrite duration: " + sw.ElapsedMilliseconds);
		}

        [Test]
        public void TestCreateWorkerWithPortAsBaseClass()
        {
            MyWorker portsetWorker = new MyWorker();
			Dictionary<Type, IPort> messageContractPorts = wf.GetMessageContractPorts(portsetWorker);

			Assert.AreEqual(portsetWorker.P1, wf.GetPortForMessageType(messageContractPorts, typeof(string)));

            SinglePortWorker portWorker = new SinglePortWorker();
			messageContractPorts = wf.GetMessageContractPorts(portWorker);
			Assert.AreEqual(portWorker, wf.GetPortForMessageType(messageContractPorts, typeof(string)));
        }

		[Test]
		[ExpectedException(typeof(XcoWorkerException))]
		public void WorkerWithInvalidProcessorMethod()
		{
			wf.Create<SinglePortWorkerInvalidProcessor, SinglePortWorkerInvalidProcessor>(null);
		}
    }
}
